package com.jzo2o.foundations.service.impl;

import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.CollUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.es.core.ElasticSearchTemplate;
import com.jzo2o.es.utils.SearchResponseUtils;
import com.jzo2o.foundations.constants.RedisConstants;
import com.jzo2o.foundations.enums.FoundationStatusEnum;
import com.jzo2o.foundations.enums.HotStatusEnum;
import com.jzo2o.foundations.mapper.ServeMapper;
import com.jzo2o.foundations.mapper.ServeSyncMapper;
import com.jzo2o.foundations.model.domain.*;
import com.jzo2o.foundations.model.dto.request.ServePageQueryReqDTO;
import com.jzo2o.foundations.model.dto.request.ServeUpsertReqDTO;
import com.jzo2o.foundations.model.dto.response.ServeAggregationSimpleResDTO;
import com.jzo2o.foundations.model.dto.response.ServeCategoryResDTO;
import com.jzo2o.foundations.model.dto.response.ServeResDTO;
import com.jzo2o.foundations.model.dto.response.ServeSimpleResDTO;
import com.jzo2o.foundations.service.IRegionService;
import com.jzo2o.foundations.service.IServeItemService;
import com.jzo2o.foundations.service.IServeService;
import com.jzo2o.foundations.service.IServeTypeService;
import com.jzo2o.mysql.utils.PageHelperUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 区域服务实现类
 * @author JIAN
 */
@Service
public class ServeServiceImpl extends ServiceImpl<ServeMapper, Serve> implements IServeService {
    @Override
    @Cacheable(value = RedisConstants.CacheName.SERVE, key = "#id", cacheManager = RedisConstants.CacheManager.ONE_DAY)
    public Serve selectByIdCache(Long id) {
        return baseMapper.selectById(id);
    }

    @Override
    public PageResult<ServeResDTO> page(ServePageQueryReqDTO servePageQueryReqDTO) {
        return PageHelperUtils.selectPage(servePageQueryReqDTO,
                () -> baseMapper.queryServeListByRegionId(servePageQueryReqDTO.getRegionId()));
    }

    @Override
    @Transactional
    public void addBatch(List<ServeUpsertReqDTO> serveItems) {
        for (ServeUpsertReqDTO serveItemDTO : serveItems) {
            // 服务项校验
            Long serveItemId = serveItemDTO.getServeItemId();
            ServeItem serveItem = serveItemService.getById(serveItemId);
            if (ObjectUtils.isEmpty(serveItem)) {
                throw new ForbiddenOperationException("服务项不存在");
            }
            if (!serveItem.getActiveStatus().equals(FoundationStatusEnum.ENABLE.getStatus())) {
                throw new ForbiddenOperationException("服务项未启用");
            }

            // 重复添加校验
            Long regionId = serveItemDTO.getRegionId();
            Integer count = lambdaQuery()
                    .eq(Serve::getServeItemId, serveItemId)
                    .eq(Serve::getRegionId, regionId)
                    .count();
            if (count > 0) {
                throw new ForbiddenOperationException("服务项已添加");
            }

            Serve serve = BeanUtils.toBean(serveItemDTO, Serve.class);
            serve.setCityCode(regionService.getById(regionId).getCityCode());
            baseMapper.insert(serve);
        }
    }

    /**
     * 根据id获取服务不存在则抛出异常
     * @throws ForbiddenOperationException 拒绝异常
     */
    private Serve getServeByIdIfExist(Long id) throws ForbiddenOperationException {
        Serve serve = baseMapper.selectById(id);
        if (ObjectUtils.isEmpty(serve)) {
            throw new ForbiddenOperationException("所选服务不存在");
        }
        return serve;
    }

    @Override
    @Transactional
    @CachePut(value = RedisConstants.CacheName.SERVE, key = "#id", unless = "#result.saleStatus != 2", cacheManager = RedisConstants.CacheManager.ONE_DAY)
    public Serve updatePrice(Long id, BigDecimal price) {
        // 区域服务检验
        Serve serve = getServeByIdIfExist(id);

        if (!lambdaUpdate()
                .eq(Serve::getId, id)
                .set(Serve::getPrice, price)
                .update()) {
            throw new CommonException("价格更新失败");
        }

        // 更新sync数据同步ES
        ChainWrappers.lambdaUpdateChain(serveSyncMapper)
                .eq(ServeSync::getId, id)
                .set(ServeSync::getPrice, price)
                .update();

        // 返回修改后对象同步缓存
        serve.setPrice(price);
        return serve;
    }

    @Override
    @Transactional
    @CachePut(value = RedisConstants.CacheName.SERVE, key = "#id", cacheManager = RedisConstants.CacheManager.ONE_DAY)
    public Serve updateOnSale(Long id) {
        // 区域服务检验
        Serve serve = getServeByIdIfExist(id);

        Integer saleStatus = serve.getSaleStatus();
        if (!ObjectUtils.equal(saleStatus, FoundationStatusEnum.INIT.getStatus())
                && !ObjectUtils.equal(saleStatus, FoundationStatusEnum.DISABLE.getStatus())) {
            throw new ForbiddenOperationException("草稿或下架状态才能上架");
        }

        Integer activeStatus = serveItemService.getById(serve.getServeItemId()).getActiveStatus();
        if (!ObjectUtils.equal(activeStatus, FoundationStatusEnum.ENABLE.getStatus())) {
            throw new ForbiddenOperationException("对应的服务项未启用不能上架");
        }

        int enableStatus = FoundationStatusEnum.ENABLE.getStatus();
        if (!lambdaUpdate()
                .eq(Serve::getId, id)
                .set(Serve::getSaleStatus, enableStatus)
                .update()) {
            throw new CommonException("状态更新失败");
        }

        // 更新服务启用状态
        serve.setSaleStatus(enableStatus);
        // 服务项信息
        ServeItem serveItem = serveItemService.getById(serve.getServeItemId());
        // 服务类型
        ServeType serveType = serveTypeService.getById(serveItem.getServeTypeId());

        // 插入sync表添加ES索引
        serveSyncMapper.insert(ServeSync.builder()
                // serve_type data
                .serveTypeId(serveType.getId())
                .serveTypeName(serveType.getName())
                .serveItemIcon(serveType.getServeTypeIcon())
                .serveTypeImg(serveType.getImg())
                .serveTypeSortNum(serveType.getSortNum())
                // serve_item data
                .serveItemId(serveItem.getId())
                .serveItemName(serveItem.getName())
                .serveItemIcon(serveItem.getServeItemIcon())
                .serveItemImg(serveItem.getImg())
                .serveItemSortNum(serveItem.getSortNum())
                .unit(serveItem.getUnit())
                .detailImg(serveItem.getDetailImg())
                // serve data
                .id(serve.getId())
                .price(serve.getPrice())
                .cityCode(serve.getCityCode())
                .isHot(serve.getIsHot()).build());

        return serve;
    }

    @Override
    @Transactional
    @CacheEvict(value = RedisConstants.CacheName.SERVE, key = "#id", cacheManager = RedisConstants.CacheManager.ONE_DAY)
    public void updateOffSale(Long id) {
        // 区域服务检验
        Serve serve = getServeByIdIfExist(id);

        Integer saleStatus = serve.getSaleStatus();
        if (!ObjectUtils.equal(saleStatus, FoundationStatusEnum.ENABLE.getStatus())) {
            throw new ForbiddenOperationException("服务为上架状态才能下架");
        }

        int disableStatus = FoundationStatusEnum.DISABLE.getStatus();
        if (!lambdaUpdate()
                .eq(Serve::getId, id)
                .set(Serve::getSaleStatus, disableStatus)
                .update()) {
            throw new CommonException("状态更新失败");
        }

        // 删除sync表数据删除ES索引
        serveSyncMapper.deleteById(id);
    }

    @Override
    @Transactional
    public void deleteById(Long id) {
        Serve serve = getServeByIdIfExist(id);

        Integer saleStatus = serve.getSaleStatus();
        if (!ObjectUtils.equal(saleStatus, FoundationStatusEnum.INIT.getStatus())) {
            throw new ForbiddenOperationException("服务为草稿状态才能删除");
        }

        baseMapper.deleteById(id);
    }

    @Override
    @Transactional
    public void updateOnHot(Long id) {
        Serve serve = getServeByIdIfExist(id);

        if (serve.getIsHot() != HotStatusEnum.OFF_HOT.getStatus()) {
            throw new RuntimeException("请勿重复设置热门");
        }

        int hotStatus = HotStatusEnum.ON_HOT.getStatus();
        if (!lambdaUpdate()
                .eq(Serve::getId, id)
                .set(Serve::getIsHot, hotStatus)
                .update()) {
            throw new CommonException("热门状态更新失败");
        }
    }

    @Override
    @Transactional
    public void updateOffHot(Long id) {
        Serve serve = getServeByIdIfExist(id);

        if (serve.getIsHot() != HotStatusEnum.ON_HOT.getStatus()) {
            throw new RuntimeException("请勿重复取消热门");
        }

        int hotStatus = HotStatusEnum.OFF_HOT.getStatus();
        if (!lambdaUpdate()
                .eq(Serve::getId, id)
                .set(Serve::getIsHot, hotStatus)
                .update()) {
            throw new CommonException("热门状态更新失败");
        }
    }

    @Override
    @Caching(cacheable = {
            @Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES,
                    unless = "#result.size() != 0"), // 防止缓存穿透
            @Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.FOREVER,
                    unless = "#result.size() == 0")})
    public List<ServeCategoryResDTO> getFirstPageServeList(Long regionId) {
        Region region = regionService.getById(regionId);
        if (ObjectUtils.isEmpty(region)) {
            return Collections.emptyList();
        }

        List<ServeCategoryResDTO> serveIconList = baseMapper.queryServeIconListByRegionId(regionId);
        if (CollUtils.isEmpty(serveIconList)) {
            return Collections.emptyList();
        }

        // 保留最多前两个类型及最多前4个服务项
        serveIconList = new ArrayList<>(serveIconList.subList(0, Math.min(serveIconList.size(), 2)));
        serveIconList.forEach(res -> {
            List<ServeSimpleResDTO> serveResDTOList = res.getServeResDTOList();
            res.setServeResDTOList(new ArrayList<>(serveResDTOList.subList(0, Math.min(serveResDTOList.size(), 4))));
        });

        return serveIconList;
    }

    @Override
    @Caching(cacheable = {
            @Cacheable(value = RedisConstants.CacheName.HOT_SERVE, key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES,
                    unless = "#result.size() != 0"), // 防止缓存穿透
            @Cacheable(value = RedisConstants.CacheName.HOT_SERVE, key = "#regionId",
                    cacheManager = RedisConstants.CacheManager.FOREVER,
                    unless = "#result.size() == 0")})
    public List<ServeAggregationSimpleResDTO> getHotServeList(Long regionId) {
        Region region = regionService.getById(regionId);
        if (ObjectUtils.isEmpty(region)) {
            return Collections.emptyList();
        }

        List<ServeAggregationSimpleResDTO> hotServeList = baseMapper.queryHotServeListByRegionId(regionId);
        if (CollUtils.isEmpty(hotServeList)) {
            return Collections.emptyList();
        }

        return hotServeList;
    }

    @Override
    public ServeAggregationSimpleResDTO getServeById(Long serveId) {
        Serve serve = serveService.selectByIdCache(serveId);
        if (ObjectUtils.isEmpty(serve)) {
            return new ServeAggregationSimpleResDTO();
        }

        ServeItem serveItem = serveItemService.selectByIdCache(serve.getServeItemId());
        if (ObjectUtils.isEmpty(serveItem)) {
            return new ServeAggregationSimpleResDTO();
        }

        return ServeAggregationSimpleResDTO.builder()
                .id(serve.getId())
                .serveItemId(serveItem.getId())
                .serveItemName(serveItem.getName())
                .serveItemImg(serveItem.getImg())
                .detailImg(serveItem.getDetailImg())
                .unit(serveItem.getUnit())
                .price(serve.getPrice())
                .cityCode(serve.getCityCode())
                .build();
    }

    @Override
    public List<ServeSimpleResDTO> findServeList(String cityCode, Long serveTypeId, String keyword) {
        // 构建ES搜索请求
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index("serve_aggregation")
                .query(query -> query
                        .bool(bool -> {
                            bool = bool.must(must -> must.term(term -> term
                                    .field("city_code")
                                    .value(cityCode)));

                            if (!ObjectUtils.isEmpty(serveTypeId)) {
                                bool = bool.must(must -> must.term(term -> term
                                        .field("serve_type_id")
                                        .value(serveTypeId)));
                            }

                            if (!ObjectUtils.isEmpty(keyword)) {
                                bool = bool.must(must -> must.multiMatch(multi -> multi
                                        .query(keyword)
                                        .fields("serve_item_name", "serve_item_name")));
                            }

                            return bool;
                        }))
                .sort(sort -> sort
                        .field(field -> field
                                .field("serve_item_sort_num")
                                .order(SortOrder.Asc)))
                .build();

        // 发送搜索请求获取结果
        SearchResponse<ServeAggregation> response = elasticSearchTemplate
                .opsForDoc()
                .search(searchRequest, ServeAggregation.class);

        if (SearchResponseUtils.isNotSuccess(response)) {
            return Collections.emptyList();
        } else {
            return response.hits().hits().stream()
                    .map(hit -> BeanUtils.toBean(hit.source(), ServeSimpleResDTO.class))
                    .collect(Collectors.toList());
        }
    }

    @Override
    public ServeAggregationResDTO getServeAggregationById(Long id) {
        return baseMapper.getServeAggregationById(id);
    }

    @Resource
    private IRegionService regionService;
    @Resource
    private IServeService serveService;
    @Resource
    private IServeTypeService serveTypeService;
    @Resource
    private IServeItemService serveItemService;
    @Resource
    private ServeSyncMapper serveSyncMapper;
    @Resource
    private ElasticSearchTemplate elasticSearchTemplate;
}